package drds.binlog.database_driver.packets;

import drds.binlog.common.Authors;
import drds.binlog.database_driver.utils.Bytes;
import drds.common.Author;
import lombok.Getter;
import lombok.Setter;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

@Author(name = Authors.LI_YANG)
public class UuidSet
{

    @Setter
    @Getter
    public UUID SID;
    @Setter
    @Getter
    public List<Interval> intervals;

    /**
     * 解析如下格式字符串为UUIDSet: 726757ad-4455-11e8-ae04-0242ac110002:1 => UuidSet{SID:
     * 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:2}]}
     * 726757ad-4455-11e8-ae04-0242ac110002:1-3 => UuidSet{SID:
     * 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4}]}
     * 726757ad-4455-11e8-ae04-0242ac110002:1-3:4 UuidSet{SID:
     * 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:5}]}
     * 726757ad-4455-11e8-ae04-0242ac110002:1-3:7-9 UuidSet{SID:
     * 726757ad-4455-11e8-ae04-0242ac110002, intervals: [{start:1, stop:4},
     * {start:7, stop:10}]}
     *
     * @param string
     * @return
     */
    public static UuidSet parse(String string)
    {
        String[] strings = string.split(":");

        if (strings.length < 2)
        {
            throw new RuntimeException(String.format("parseUUIDSet failed due to wrong format: %s", string));
        }

        List<Interval> intervals = new ArrayList<Interval>();
        for (int i = 1; i < strings.length; i++)
        {
            intervals.add(parseInterval(strings[i]));
        }

        UuidSet uuidSet = new UuidSet();
        uuidSet.SID = UUID.fromString(strings[0]);
        uuidSet.intervals = combine(intervals);

        return uuidSet;
    }

    /**
     * 解析如下格式字符串为Interval: 1 => Interval{start:1, stop:2} 1-3 =>
     * Interval{start:1, stop:4} 注意！字符串格式表达时[n,m]是两侧都包含的，Interval表达时[n,m)右侧开
     *
     * @param string
     * @return
     */
    public static Interval parseInterval(String string)
    {
        String[] strings = string.split("-");

        Interval interval = new Interval();
        switch (strings.length)
        {
            case 1:
                interval.start = Long.parseLong(strings[0]);
                interval.stop = interval.start + 1;
                break;
            case 2:
                interval.start = Long.parseLong(strings[0]);
                interval.stop = Long.parseLong(strings[1]) + 1;
                break;
            default:
                throw new RuntimeException(String.format("parseInterval failed due to wrong format: %s", string));
        }

        return interval;
    }

    /**
     * 把{start,stop}连续的合并掉: [{start:1, stop:4},{start:4, stop:5}] => [{start:1,
     * stop:5}]
     *
     * @param intervalList
     * @return
     */
    public static List<Interval> combine(List<Interval> intervalList)
    {
        List<Interval> combined = new ArrayList<Interval>();
        Collections.sort(intervalList);
        int len = intervalList.size();
        for (int i = 0; i < len; i++)
        {
            combined.add(intervalList.get(i));

            int j;
            for (j = i + 1; j < len; j++)
            {
                if (intervalList.get(i).stop >= intervalList.get(j).start)
                {
                    intervalList.get(i).stop = intervalList.get(j).stop;
                } else
                {
                    break;
                }
            }
            i = j - 1;
        }

        return combined;
    }

    public byte[] encode() throws IOException
    {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
        byteBuffer.putLong(SID.getMostSignificantBits());
        byteBuffer.putLong(SID.getLeastSignificantBits());

        byteArrayOutputStream.write(byteBuffer.array());

        Bytes.writeUnsigned8IntLittleEndian(byteArrayOutputStream, intervals.size());

        for (Interval interval : intervals)
        {
            Bytes.writeUnsigned8IntLittleEndian(byteArrayOutputStream, interval.start);
            Bytes.writeUnsigned8IntLittleEndian(byteArrayOutputStream, interval.stop);
        }

        return byteArrayOutputStream.toByteArray();
    }

    @Override
    public boolean equals(Object object)
    {
        if (object == null) return false;
        if (this == object) return true;

        UuidSet uuidSet = (UuidSet) object;
        Collections.sort(intervals);
        Collections.sort(uuidSet.intervals);
        if (SID.equals(uuidSet.SID) && intervals.equals(uuidSet.intervals))
        {
            return true;
        }

        return false;
    }

    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder();

        sb.append(SID.toString());
        for (Interval interval : intervals)
        {
            if (interval.start == interval.stop - 1)
            {
                sb.append(":");
                sb.append(interval.start);
            } else
            {
                sb.append(":");
                sb.append(interval.start);
                sb.append("-");
                sb.append(interval.stop - 1);
            }
        }

        return sb.toString();
    }

    public static class Interval implements Comparable<Interval>
    {

        public long start;
        public long stop;

        @Override
        public boolean equals(Object o)
        {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Interval interval = (Interval) o;

            if (start != interval.start) return false;
            return stop == interval.stop;
        }

        @Override
        public int hashCode()
        {
            int result = (int) (start ^ (start >>> 32));
            result = 31 * result + (int) (stop ^ (stop >>> 32));
            return result;
        }

        @Override
        public int compareTo(Interval o)
        {
            if (equals(o))
            {
                return 1;
            }
            return start > o.start ? 1 : (start == o.start ? 0 : -1);
        }
    }
}
